

// cSignalSlot.h
// by ElmueSoft  (www.elmue.de.vu  or  kickme.to/elmue)
// This class is platform independent and should theoretically run on all compilers
// This class is optimized for high speed


#if !defined(SIGNAL_SLOT_ELMUESOFT_H)
#define SIGNAL_SLOT_ELMUESOFT_H


#include "Callback.h"
#include "sys\timeb.h"
#include <vector>

#pragma warning (disable:4996)

/***********************************************************
///////////////////   naming conventions   /////////////////
************************************************************


Using these conventions results in better readable code and less coding errors !


     cName  for class definitions
     tName  for type  definitions
     eName  for enum  definitions

    e_Name  for enum variables
    E_Name  for enum constant values

    i_Name  for instances of classes
    h_Name  for handles

    T_Name  for Templates

    s_Name  for strings
    r_Name  for return values
    f_Name  for function pointers
    k_Name  for contructs (struct)

    b_Name      bool  1 Bit

   s8_Name    signed  8 Bit (char)
  s16_Name    signed 16 Bit (SHORT, WCHAR)
  s32_Name    signed 32 Bit (LONG, int)
  s64_Name    signed 64 Bit (LONGLONG)

   u8_Name  unsigned  8 Bit (BYTE)
  u16_Name  unsigned 16 bit (WORD)
  u32_Name  unsigned 32 Bit (DWORD, UINT)
  u64_Name  unsigned 64 Bit (ULONGLONG)

    d_Name  for double

  ----------------

    m_Name  for member variables of a class (e.g. ms32_Name for int member variable)
    g_Name  for global variables            (e.g. gu16_Name for global WORD)
    p_Name  for pointer                     (e.g.   ps_Name pointer to string)
   pp_Name  for pointer to pointer          (e.g.  ppd_Name **pointer to double)



************************************************************
/////////////////////////   BASE   /////////////////////////
***********************************************************/

// abstract base class for both: signals and slots

template <class tRet, class tArg1=UNUSABLE,
                      class tArg2=UNUSABLE,
                      class tArg3=UNUSABLE,
                      class tArg4=UNUSABLE,
                      class tArg5=UNUSABLE>
class cSigSlotBase
{
protected:
    // --- base definitions

    typedef typename CLASS_SWITCH(IS_VOID(tRet), UNUSABLE, tRet)  ret_type;

    typedef cSigSlotBase<tRet, tArg1, tArg2, tArg3, tArg4, tArg5> tBase;

    typedef typename std::vector<tBase*>           tList;
    typedef typename std::vector<tBase*>::iterator tIter;

    enum eType { E_Slot, E_Signal };

    // --- base members

    tList          mi_List;           // list of connected slots / signals
    eType          me_Type;           // either E_Slot or E_Signal
    char           *mps8_MyName;      // user defined name only for debugging

    unsigned int mu32_MinInterval;    // minimum interval between two fire events
    ret_type       mr_DefaultInterval;// value to return if signal does not fire
    double         md_LastFire;       // time in ms since 1970 of last fire request

    bool           mb_Reentrance;     // catch reentrant calls
    ret_type       mr_DefaultReenter; // return value if signal/slot is called reentrant

    // --- base functions

    // find a signal or slot in the connected list
    bool Find(tBase &i_Base, tIter &i_Pos)
    {
        for (tIter i=mi_List.begin(); i!=mi_List.end(); i++)
        {
            if (*i == &i_Base)
            {
                i_Pos = i;
                return true;
            }
        }
        return false;
    }

    // remove a signal or slot from the connected list
    void Remove(tBase &i_Base)
    {
        tIter i;
        if (Find(i_Base, i)) mi_List.erase(i);
    }

    // insert a new signal or slot at the beginning or end of the connected list
    void Insert(tBase &i_Base, bool b_Append=true)
    {
        tIter i;
        if (!Find(i_Base, i))  // check if connection already exists
        {
            mi_List.insert(b_Append ? mi_List.end() : mi_List.begin(), &i_Base);
        }
    }

    // return false if the elapsed time between 2 fire requests is < mu32_MinInterval
    bool CheckInterval()
    {
        if (!mu32_MinInterval)
            return true;

        struct _timeb k_Now;
        _ftime (&k_Now);

        // calc current time since 1970 in milliseconds
        double d_Now = ((double) k_Now.time) * 1000 + k_Now.millitm;

        bool b_Ret = (md_LastFire + mu32_MinInterval <= d_Now);

        md_LastFire = d_Now;
        return b_Ret;
    }

    // will be overwritten only by a slot
    virtual bool Valid()
    {
        return true;
    }

public:
    virtual ret_type Fire() =0;
    virtual ret_type Fire(tArg1 Arg1) =0;
    virtual ret_type Fire(tArg1 Arg1, tArg2 Arg2) =0;
    virtual ret_type Fire(tArg1 Arg1, tArg2 Arg2, tArg3 Arg3) =0;
    virtual ret_type Fire(tArg1 Arg1, tArg2 Arg2, tArg3 Arg3, tArg4 Arg4) =0;
    virtual ret_type Fire(tArg1 Arg1, tArg2 Arg2, tArg3 Arg3, tArg4 Arg4, tArg5 Arg5) =0;

    // connect a signal with a slot or a slot with a signal
    void Connect(tBase &i_Base, bool b_Append=true)
    {
        // you cannot connect a slot or a signal to itself !
        if (&i_Base == this)
            { assert(0); return; }

        // you cannot connect a slot to a slot or a signal to a signal
        if (me_Type == i_Base.me_Type)
            { assert(0); return; }

        // You have to call AssignFunction() on the slot before connecting it
        if (!i_Base.Valid() || !Valid())
            { assert(0); return; }

        i_Base.Insert(*this, b_Append);
        Insert(i_Base, b_Append);
    }

    // disconnect a signal from this slot or a slot from this signal
    void Disconnect(tBase &i_Base)
    {
        i_Base.Remove(this);
        Remove(i_Base);
    }

    void DisconnectAll()
    {
        for (unsigned int i=0; i<mi_List.size(); i++)
        {
            mi_List.at(i)->Remove(*this);
        }
        mi_List.clear();
    }

    // set the timeout to ignore series of Fire() events
    // and the default return value to return if the fire event is blocked
    void SetMinInterval(unsigned int u32_Milliseconds, ret_type r_ReturnValue)
    {
        mu32_MinInterval   = u32_Milliseconds;
        mr_DefaultInterval = r_ReturnValue;
    }

    // get the number of slots connected to this signal
    // or the number of signals connected to this slot
    int GetConnectedCount()
    {
        return mi_List.size();
    }

    // for debugging purposes
    // lists all slots / signals connected to this signal / slot
    // return false if buffer too small
    // For higher speed I don't use string here
    bool GetConnectedList(char *ps8_Buf, int s32_BufLen)
    {
        if (s32_BufLen < (int)strlen(mps8_MyName) +50)
            return false;

        sprintf(ps8_Buf, "%s %s is connected to: ", (me_Type==E_Slot?"Slot":"Signal"), mps8_MyName);

        if (!mi_List.size()) strcat(ps8_Buf, "Nothing\n");
        else
        {
            s32_BufLen -= strlen(ps8_Buf);

            for (unsigned int i=0; i<mi_List.size(); i++)
            {
                char *ps8_Name = mi_List.at(i)->mps8_MyName;

                s32_BufLen -= (strlen(ps8_Name) +2);
                if (s32_BufLen <= 5)
                    return false;

                if (i) strcat(ps8_Buf, ", ");
                strcat(ps8_Buf, ps8_Name);
            }
        }
        return true;
    }

    // constructor (signal + slot)
    cSigSlotBase()
    {
        md_LastFire        = 0;
        mu32_MinInterval   = 0;
    }

    // destructor (disonnects all connected signals / slots)
    ~cSigSlotBase()
    {
        DisconnectAll();
    }
};


/***********************************************************
/////////////////////////   SIGNAL   ///////////////////////
***********************************************************/

// for callbacks to Member functions
#define MAKE_COMBINE_M(Class, Instance, Function, RetType) cCallGen<Class, bool, RetType*, RetType>(Instance, Function)
// for callbacks to Static functions
#define MAKE_COMBINE_S(                 Function, RetType) cCallGenS<      bool, RetType*, RetType>(          Function)


template <class tRet, class tArg1=UNUSABLE,
                      class tArg2=UNUSABLE,
                      class tArg3=UNUSABLE,
                      class tArg4=UNUSABLE,
                      class tArg5=UNUSABLE>
class cSignal : public cSigSlotBase<tRet, tArg1, tArg2, tArg3, tArg4, tArg5>
{
protected:

    ret_type    mr_DefaultCombine;               // return value if no slot connected
    cCall<bool, ret_type*, ret_type> mi_Combine; // function to combine the slots return values

    // this function does nothing, it will be replaced when
    // SetCombineFunction() is called and a "real" combine function was set
    bool CombineDummy(ret_type *p_Result, ret_type r_NewValue)
    {
        *p_Result = ret_type();
        return true; // true = continue calling the next slot in the connected list
    }

public:
    // constructor
    cSignal(ret_type r_DefaultReenter = (ret_type) 0,
            char     *ps8_Name        = "NoName-Signal")
    {
        mps8_MyName       = ps8_Name;
        me_Type           = E_Signal;
        mr_DefaultReenter = r_DefaultReenter;
        mb_Reentrance     = false;
        mi_Combine        = MAKE_COMBINE_M(cSignal, this, &cSignal::CombineDummy, ret_type);
    }

    // set a function to combine the return values from all connected slots
    // to one single return value
    // This function may be a static or a member function
    void SetCombineFunction(cCall<bool, ret_type*, ret_type> i_Callback, ret_type r_DefaultRetVal)
    {
        mi_Combine        = i_Callback;
        mr_DefaultCombine = r_DefaultRetVal;
    }

    // fires an event. All connected slots will be fired
    // if the combine function returns false, no more slots are fired
    // if the interval since the last fire event has not yet elapsed,
    // the default return value is returned which was set in SetMinInterval()
    ret_type Fire()
    {
        if (mb_Reentrance)    return mr_DefaultReenter;
        if (!CheckInterval()) return mr_DefaultInterval;

        mb_Reentrance     = true;
        ret_type r_Result = mr_DefaultCombine;

        for (tIter i=mi_List.begin(); i!=mi_List.end(); i++)
        {
            if (!mi_Combine.Execute(&r_Result, (*i)->Fire()))
                break;
        }
        mb_Reentrance = false;
        return r_Result;
    }

    ret_type Fire(tArg1 Arg1)
    {
        if (mb_Reentrance)    return mr_DefaultReenter;
        if (!CheckInterval()) return mr_DefaultInterval;

        mb_Reentrance     = true;
        ret_type r_Result = mr_DefaultCombine;

        for (tIter i=mi_List.begin(); i!=mi_List.end(); i++)
        {
            if (!mi_Combine.Execute(&r_Result, (*i)->Fire(Arg1)))
                break;
        }
        mb_Reentrance = false;
        return r_Result;
    }

    ret_type Fire(tArg1 Arg1, tArg2 Arg2)
    {
        if (mb_Reentrance)    return mr_DefaultReenter;
        if (!CheckInterval()) return mr_DefaultInterval;

        mb_Reentrance     = true;
        ret_type r_Result = mr_DefaultCombine;

        for (tIter i=mi_List.begin(); i!=mi_List.end(); i++)
        {
            if (!mi_Combine.Execute(&r_Result, (*i)->Fire(Arg1, Arg2)))
                break;
        }
        mb_Reentrance = false;
        return r_Result;
    }

    ret_type Fire(tArg1 Arg1, tArg2 Arg2, tArg3 Arg3)
    {
        if (mb_Reentrance)    return mr_DefaultReenter;
        if (!CheckInterval()) return mr_DefaultInterval;

        mb_Reentrance     = true;
        ret_type r_Result = mr_DefaultCombine;

        for (tIter i=mi_List.begin(); i!=mi_List.end(); i++)
        {
            if (!mi_Combine.Execute(&r_Result, (*i)->Fire(Arg1, Arg2, Arg3)))
                break;
        }
        mb_Reentrance = false;
        return r_Result;
    }

    ret_type Fire(tArg1 Arg1, tArg2 Arg2, tArg3 Arg3, tArg4 Arg4)
    {
        if (mb_Reentrance)    return mr_DefaultReenter;
        if (!CheckInterval()) return mr_DefaultInterval;

        mb_Reentrance     = true;
        ret_type r_Result = mr_DefaultCombine;

        for (tIter i=mi_List.begin(); i!=mi_List.end(); i++)
        {
            if (!mi_Combine.Execute(&r_Result, (*i)->Fire(Arg1, Arg2, Arg3, Arg4)))
                break;
        }
        mb_Reentrance = false;
        return r_Result;
    }

    ret_type Fire(tArg1 Arg1, tArg2 Arg2, tArg3 Arg3, tArg4 Arg4, tArg5 Arg5)
    {
        if (mb_Reentrance)    return mr_DefaultReenter;
        if (!CheckInterval()) return mr_DefaultInterval;

        mb_Reentrance     = true;
        ret_type r_Result = mr_DefaultCombine;

        for (tIter i=mi_List.begin(); i!=mi_List.end(); i++)
        {
            if (!mi_Combine.Execute(&r_Result, (*i)->Fire(Arg1, Arg2, Arg3, Arg4, Arg5)))
                break;
        }
        mb_Reentrance = false;
        return r_Result;
    }
};


/***********************************************************
/////////////////////////   SLOT   /////////////////////////
***********************************************************/


template <class cInstance, class tRet, class tArg1=UNUSABLE,
                                       class tArg2=UNUSABLE,
                                       class tArg3=UNUSABLE,
                                       class tArg4=UNUSABLE,
                                       class tArg5=UNUSABLE>
class cSlot
    : public cSigSlotBase<tRet, tArg1, tArg2, tArg3, tArg4, tArg5>
{
private:
    typedef tRet (cInstance::*tFunction0)();
    typedef tRet (cInstance::*tFunction1)(tArg1);
    typedef tRet (cInstance::*tFunction2)(tArg1, tArg2);
    typedef tRet (cInstance::*tFunction3)(tArg1, tArg2, tArg3);
    typedef tRet (cInstance::*tFunction4)(tArg1, tArg2, tArg3, tArg4);
    typedef tRet (cInstance::*tFunction5)(tArg1, tArg2, tArg3, tArg4, tArg5);

    // Choose the correct argument count
    typedef typename CLASS_SWITCH(IS_USABLE(tArg5), tFunction5, tFunction4)  tFunctionA;
    typedef typename CLASS_SWITCH(IS_USABLE(tArg4), tFunctionA, tFunction3)  tFunctionB;
    typedef typename CLASS_SWITCH(IS_USABLE(tArg3), tFunctionB, tFunction2)  tFunctionC;
    typedef typename CLASS_SWITCH(IS_USABLE(tArg2), tFunctionC, tFunction1)  tFunctionD;
    typedef typename CLASS_SWITCH(IS_USABLE(tArg1), tFunctionD, tFunction0)  tFunction;

    cCall<tRet, tArg1, tArg2, tArg3, tArg4, tArg5> mi_CallFire;

protected:
    // to check if the user forgot to call AssignFunction() (see comment above!)
    bool Valid()
    {
        return mi_CallFire.Valid();
    }

public:
    /* -----------------------------------------------------------------------------------------
       It would be nice to move AssignFunction() into the constructor, but this is not possible:
       If the slot is a member of its owner class it would have to be initialized in the
       member initialization list. But there the "this" pointer is not available:

       cMyClass(....) : mi_MySlot(this, cMyClass::Function, "mi_MySlot") would not compile!

       Calling Fire() on an uninitialized slot (a slot with no function attached)
       will assert in mi_CallFire. Trying to Connect() an uninitialized slot also asserts.
    ------------------------------------------------------------------------------------------- */

    // constructor (see comment above!)
    cSlot(ret_type r_DefaultReenter = (ret_type) 0)
    {
        mr_DefaultReenter = r_DefaultReenter;
        mb_Reentrance     = false;

        mps8_MyName = "Uninitialized-Slot";
        me_Type     = E_Slot;
    }

    // sets the callback to a C++ member function to be executed when Fire() is called (see comment above!)
    void AssignFunction(cInstance *p_Inst, tFunction p_Func, char *ps8_Name = "NoName-Slot")
    {
        mi_CallFire = cCallGen<cInstance, tRet, tArg1, tArg2, tArg3, tArg4, tArg5>(p_Inst, p_Func);

        if (mi_CallFire.Valid()) mps8_MyName = ps8_Name;
    }

    // sets the callback to a static function to be executed when Fire() is called (see comment above!)
    void AssignFunction(tFunction p_Func, char *ps8_Name = "NoName-Slot")
    {
        mi_CallFire = cCallGenS<tRet, tArg1, tArg2, tArg3, tArg4, tArg5>(p_Func);

        if (mi_CallFire.Valid()) mps8_MyName = ps8_Name;
    }

    // fires this slot ( = calls the attached callback function)
    // if the interval since last fire event has not yet elapsed,
    // it returns the default value which has been set in SetMinInterval()
    ret_type Fire()
    {
        if (mb_Reentrance)    return mr_DefaultReenter;
        if (!CheckInterval()) return mr_DefaultInterval;

        mb_Reentrance = true;
        ret_type r_Result = mi_CallFire.Execute();

        mb_Reentrance = false;
        return r_Result;
    }

    ret_type Fire(tArg1 Arg1)
    {
        if (mb_Reentrance)    return mr_DefaultReenter;
        if (!CheckInterval()) return mr_DefaultInterval;

        mb_Reentrance = true;
        ret_type r_Result = mi_CallFire.Execute(Arg1);

        mb_Reentrance = false;
        return r_Result;
    }

    ret_type Fire(tArg1 Arg1, tArg2 Arg2)
    {
        if (mb_Reentrance)    return mr_DefaultReenter;
        if (!CheckInterval()) return mr_DefaultInterval;

        mb_Reentrance = true;
        ret_type r_Result = mi_CallFire.Execute(Arg1, Arg2);

        mb_Reentrance = false;
        return r_Result;
    }

    ret_type Fire(tArg1 Arg1, tArg2 Arg2, tArg3 Arg3)
    {
        if (mb_Reentrance)    return mr_DefaultReenter;
        if (!CheckInterval()) return mr_DefaultInterval;

        mb_Reentrance = true;
        ret_type r_Result = mi_CallFire.Execute(Arg1, Arg2, Arg3);

        mb_Reentrance = false;
        return r_Result;
    }

    ret_type Fire(tArg1 Arg1, tArg2 Arg2, tArg3 Arg3, tArg4 Arg4)
    {
        if (mb_Reentrance)    return mr_DefaultReenter;
        if (!CheckInterval()) return mr_DefaultInterval;

        mb_Reentrance = true;
        ret_type r_Result = mi_CallFire.Execute(Arg1, Arg2, Arg3, Arg4);

        mb_Reentrance = false;
        return r_Result;
    }

    ret_type Fire(tArg1 Arg1, tArg2 Arg2, tArg3 Arg3, tArg4 Arg4, tArg5 Arg5)
    {
        if (mb_Reentrance)    return mr_DefaultReenter;
        if (!CheckInterval()) return mr_DefaultInterval;

        mb_Reentrance = true;
        ret_type r_Result = mi_CallFire.Execute(Arg1, Arg2, Arg3, Arg4, Arg5);

        mb_Reentrance = false;
        return r_Result;
    }
};


#endif // SIGNAL_SLOT_ELMUESOFT_H

